<?php
# @version SVN: $Id: search.class.inc 580 2012-03-24 20:38:39Z novachok $

set_include_path(ROOT . LIBRARY . PATH_SEPARATOR . get_include_path());
require_once(ROOT . LIBRARY . '/Zend/Search/Lucene.php');
require_once(ROOT . LIBRARY . '/phpMorphy/common.php');


/**
 * Class for russioan morphology implementation
 */
class MorphyFilter extends Zend_Search_Lucene_Analysis_TokenFilter
{

   private $MORPHY = NULL;

   public function __construct($lang)
   {
      if (!function_exists('mb_strtolower'))
      {
         require_once 'Zend/Search/Lucene/Exception.php';
         throw new Zend_Search_Lucene_Exception('Utf8 compatible lower case filter needs mbstring extension to be enabled.');
      }

      if (empty($this->MORPHY))
      {
         $options = array(
             'storage' => PHPMORPHY_STORAGE_FILE,
             'predict_by_suffix' => true,
             'predict_by_db' => true,
             'graminfo_as_text' => true,
         );

         $dictionaries = ROOT . LIBRARY . '/phpMorphy/dicts';

         if (strlen($lang) != 2)
         {
            $lang = 'ru_RU';
         }

         $this->MORPHY = new phpMorphy($dictionaries, $lang, $options);
      }
   }

   public function normalize(Zend_Search_Lucene_Analysis_Token $srcToken)
   {

      $word = $this->MORPHY->getBaseForm(mb_strtoupper($srcToken->getTermText(), 'UTF-8'));
      if (is_array($word))
      {
         $word = reset($word);
         // TODO: подумать что делать когда у слова может быть более одной словоформы.
      } elseif (!$word)
      {
         $word = $srcToken->getTermText();
      }

      $newToken = new Zend_Search_Lucene_Analysis_Token(
                      mb_strtolower($word, 'UTF-8'),
                      $srcToken->getStartOffset(),
                      $srcToken->getEndOffset());

      $newToken->setPositionIncrement($srcToken->getPositionIncrement());

      return $newToken;
   }

}

/**
 * Search class
 */
class search extends meccanoFilters
{
   private $analizer     = array();
   private $indexer      = NULL;
   private $query        = NULL;

   private $index_folder = NULL;
   private $lang         = NULL;
   public  $fields       = array('title', 'description', 'body', 'category');


   public function __construct()
   {
      $node = tree_static::getNode(9);
      paginator::setItemsOnPage($node->i_onpage);

      $this->lang = request::getLang('currentId');
      $langs = request::getLang('all');
      foreach ($langs AS $l)
      {
         $this->analizer[$l['id']] = new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8_CaseInsensitive();
         $this->analizer[$l['id']]->addFilter(new MorphyFilter($l['iso']));
      }


      $this->index_folder = ROOT . CACHE . '/search.index';
      if (file_exists($this->index_folder . '/write.lock.file'))
      {
         $this->indexer = Zend_Search_Lucene::open($this->index_folder);
      } else
      {
         $this->indexer = Zend_Search_Lucene::create($this->index_folder);
      }
   }


   /**
    * Attribute setter
    *
    * @param array $params
    */
   public function attrSetter($params)
   {
      if(is_array($params))
      {
         foreach($params AS $k=>$v)
         {
            switch($k)
            {
               case 'lang':
                  if($this->isValidInteger($v)) $this->lang = $v;
                  break;
            }
         }
      }
   }

   /**
    * Add Article to index
    *
    * @param array $var
    */
   public function addArticle($var)
   {
      if (is_array($var))
      {
         $searchInFields = config::read('search');
         $skip = TRUE;  # If article shouldn't be indexed - TRUE

         # Prepare amount of indexing categories
         $indexed_categories = array();
         if (!empty($searchInFields['category']))
         {
            foreach ($searchInFields['category'] AS $c)
            {
               $tmp = tree_static::getChildrenId($c);
               for ($i = 0, $t = count($tmp); $i < $t; $i++)
               {
                  $indexed_categories[$tmp[$i]] = $c;
               }
               $indexed_categories[$c] = $c;
            }
         }

         # Previous we should delete old index for given articles
         $article = reset($var);
         $this->deleteArticle($article['id'], $article['lang']);

         # Index articles
         foreach ($var AS $l => $article)
         {
            Zend_Search_Lucene_Analysis_Analyzer::setDefault($this->analizer[$l]);
            $DOC = new Zend_Search_Lucene_Document();
            $indexed = FALSE;
            $skip = FALSE;

            foreach ($searchInFields AS $field => $value)
            {
               if (in_array($field, $this->fields) && $skip == FALSE)
               {
                  switch ($field)
                  {
                     case 'category':
                        /*
                         *  Categorize articles to search in given categories, on over them
                         */
                        if (isset($indexed_categories[$article['cat']]))
                        {
                           $value = $indexed_categories[$article['cat']];
                        } elseif (in_array('all', $searchInFields['category']))
                        {
                           $value = 0;
                        } else
                        {
                           # If config given to search only in sertain categories - abort indexing articles from outside of this amount
                           $skip = TRUE;
                           break;
                        }

                        $DOC->addField(Zend_Search_Lucene_Field::Keyword($field, $value));
                        unset($value);
                        break;

                     default:
                        if(!empty($article[$field]))
                        {
                           $DOC->addField(Zend_Search_Lucene_Field::Text($field, strip_tags($article[$field])));
                           $indexed = TRUE;
                        } else
                        {
                           $DOC->addField(Zend_Search_Lucene_Field::unIndexed($field, NULL));
                        }
                        break;
                  }
               }
            }

            if ($indexed == TRUE && $skip == FALSE)
            {
               $DOC->addField(Zend_Search_Lucene_Field::Keyword('id', $article['id']));
               $DOC->addField(Zend_Search_Lucene_Field::Keyword('cat', $article['cat']));
               $DOC->addField(Zend_Search_Lucene_Field::Keyword('lang', $l));
               $DOC->addField(Zend_Search_Lucene_Field::Keyword('sdate', mysql2time(!empty($article['sdate']) ? $article['sdate'] : $article['cdate'])));

               $this->indexer->addDocument($DOC);
            }
            unset($DOC);
         }

         if ($skip == FALSE)
         {
            return TRUE;
         }
      }

      return FALSE;
   }

   /**
    * Delete Article from index
    *
    * @param integer $id
    */
   public function deleteArticle($id, $lang = NULL)
   {
      $query = new Zend_Search_Lucene_Search_Query_MultiTerm();
      $query->addTerm(new Zend_Search_Lucene_Index_Term($id, 'id'), TRUE);

      if (!empty($lang))
      {
         $query->addTerm(new Zend_Search_Lucene_Index_Term($lang, 'lang'), TRUE);
      }

      $results = $this->indexer->find($query);
      foreach ($results AS $r)
      {
         $this->indexer->delete($r->id);
      }
   }


   /**
    * Add condition to query string
    *
    * @param string $value - query string
    * @param string $field - field to query
    * @param bool $requrements - requirements of results by this value
    */
   public function addCondition($value, $field = NULL, $requrements = NULL)
   {
      if(empty($this->query))
      {
         $this->query = new Zend_Search_Lucene_Search_Query_MultiTerm();
         $this->query->addTerm(new Zend_Search_Lucene_Index_Term($this->lang, 'lang'), TRUE);
      }


      $this->query->addTerm(new Zend_Search_Lucene_Index_Term($value, $field), $requrements);
   }


   /**
    * Search metod
    *
    * @param string $query
    * @return array
    */
   public function search($query)
   {
      $this->addCondition($query, NULL, TRUE);
      $results = $this->indexer->find($this->query);
      $found   = count($results);
      paginator::setPages($found);

      $data            = array();
      $data['found']   = $found;
      $data['results'] = array();
      $data['pages']   = paginator::getPages();

      if($found)
      {
         $i           = 0;
         $slice_start = (paginator::getCurrentPage() -1) * paginator::getItemsOnPage();
         $slice_end   = paginator::getCurrentPage() * paginator::getItemsOnPage();
         $ext         = config::read('url_extention') ? $this->extention = config::read('url_extention') : 'html';

         foreach($results AS $r)
         {
            if($i < $slice_start)
            {
               $i++;
               continue;
            }

            if($i >= $slice_end)
            {
               break;
            }

            $tmp                = array();
            $tmp['id']          = $r->getDocument()->id;
            $tmp['sdate']       = $r->sdate;
            $tmp['score']       = $r->score;
            $tmp['category']    = tree_static::getNode($r->cat);
            $tmp['url']         = $tmp['category']->url . $tmp['id'] . '/';
            $tmp['href']        = $tmp['category']->url . $tmp['id'] .'.'. $ext;
            $tmp['title']       = $r->title;
            $tmp['description'] = !empty($r->description) ? $r->description : (isset($r->body) ? mb_substr($r->body, 0, 300, 'UTF-8') .' ...' : '');

            $data['results'][]  = $tmp;
            $i++;
         }
      }

      return $data;
   }


   /**
    * Get count of indexed articles
    * @return integer
    */
   public function getIndexCount()
   {
      return $this->indexer->count();
   }

   /**
    * Optiomization of Search index
    */
   public function optimizeIndex()
   {
      $this->indexer->optimize();
   }
}